home *** CD-ROM | disk | FTP | other *** search
/ Clickx 47 / Clickx 47.iso / assets / software / Miro_Installer.exe / xulrunner / chrome / toolkit.jar / content / mozapps / update / updates.js < prev    next >
Encoding:
Text File  |  2005-10-24  |  53.6 KB  |  1,546 lines

  1. /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is the Update Service.
  16.  *
  17.  * The Initial Developer of the Original Code is Google Inc.
  18.  * Portions created by the Initial Developer are Copyright (C) 2005
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *  Ben Goodger <ben@mozilla.org> (Original Author)
  23.  *
  24.  * Alternatively, the contents of this file may be used under the terms of
  25.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  26.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27.  * in which case the provisions of the GPL or the LGPL are applicable instead
  28.  * of those above. If you wish to allow use of your version of this file only
  29.  * under the terms of either the GPL or the LGPL, and not to allow others to
  30.  * use your version of this file under the terms of the MPL, indicate your
  31.  * decision by deleting the provisions above and replace them with the notice
  32.  * and other provisions required by the GPL or the LGPL. If you do not delete
  33.  * the provisions above, a recipient may use your version of this file under
  34.  * the terms of any one of the MPL, the GPL or the LGPL.
  35.  *
  36.  * ***** END LICENSE BLOCK ***** */
  37.  
  38. const nsIUpdateItem           = Components.interfaces.nsIUpdateItem;
  39. const nsIIncrementalDownload  = Components.interfaces.nsIIncrementalDownload;
  40.  
  41. const XMLNS_XUL               = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
  42.  
  43. const PREF_UPDATE_MANUAL_URL        = "app.update.url.manual";
  44. const PREF_UPDATE_NAGTIMER_DL       = "app.update.nagTimer.download";
  45. const PREF_UPDATE_NAGTIMER_RESTART  = "app.update.nagTimer.restart";
  46. const PREF_APP_UPDATE_LOG_BRANCH    = "app.update.log.";
  47. const PREF_UPDATE_TEST_LOOP         = "app.update.test.loop";
  48.  
  49. const UPDATE_TEST_LOOP_INTERVAL     = 2000;
  50.  
  51. const URI_UPDATES_PROPERTIES  = "chrome://mozapps/locale/update/updates.properties";
  52.  
  53. const STATE_DOWNLOADING       = "downloading";
  54. const STATE_PENDING           = "pending";
  55. const STATE_APPLYING          = "applying";
  56. const STATE_SUCCEEDED         = "succeeded";
  57. const STATE_DOWNLOAD_FAILED   = "download-failed";
  58. const STATE_FAILED            = "failed";
  59.  
  60. const SRCEVT_FOREGROUND       = 1;
  61. const SRCEVT_BACKGROUND       = 2;
  62.  
  63. var gConsole    = null;
  64. var gPref       = null;
  65. var gLogEnabled = { };
  66.  
  67. /**
  68.  * Logs a string to the error console. 
  69.  * @param   string
  70.  *          The string to write to the error console..
  71.  */  
  72. function LOG(module, string) {
  73.   if (module in gLogEnabled) {
  74.     dump("*** " + module + ":" + string + "\n");
  75.     gConsole.logStringMessage(string);
  76.   }
  77. }
  78.  
  79. /**
  80.  * Gets a preference value, handling the case where there is no default.
  81.  * @param   func
  82.  *          The name of the preference function to call, on nsIPrefBranch
  83.  * @param   preference
  84.  *          The name of the preference
  85.  * @param   defaultValue
  86.  *          The default value to return in the event the preference has 
  87.  *          no setting
  88.  * @returns The value of the preference, or undefined if there was no
  89.  *          user or default value.
  90.  */
  91. function getPref(func, preference, defaultValue) {
  92.   try {
  93.     return gPref[func](preference);
  94.   }
  95.   catch (e) {
  96.     LOG("General", "Failed to get preference " + preference);
  97.   }
  98.   return defaultValue;
  99. }
  100.  
  101. /**
  102.  * A set of shared data and control functions for the wizard as a whole.
  103.  */
  104. var gUpdates = {
  105.   /**
  106.    * The nsIUpdate object being used by this window (either for downloading, 
  107.    * notification or both).
  108.    */
  109.   update: null,
  110.   
  111.   /**
  112.    * The updates.properties <stringbundle> element.
  113.    */
  114.   strings: null,
  115.   
  116.   /**
  117.    * The Application brandShortName (e.g. "Firefox")
  118.    */
  119.   brandName: null,
  120.   
  121.   /**
  122.    * The <wizard> element
  123.    */
  124.   wiz: null,
  125.   
  126.   /**
  127.    * Set the state for the Wizard's control buttons (labels and disabled
  128.    * state).
  129.    * @param   backButtonLabel
  130.    *          The label to put on the Back button, or null for default.
  131.    * @param   backButtonDisabled
  132.    *          true if the Back button should be disabled, false otherwise
  133.    * @param   nextButtonLabel
  134.    *          The label to put on the Next button, or null for default.
  135.    * @param   nextButtonDisabled
  136.    *          true if the Next button should be disabled, false otherwise
  137.    * @param   finishButtonLabel
  138.    *          The label to put on the Finish button, or null for default.
  139.    * @param   finishButtonDisabled
  140.    *          true if the Finish button should be disabled, false otherwise
  141.    * @param   cancelButtonLabel
  142.    *          The label to put on the Cancel button, or null for default.
  143.    * @param   cancelButtonDisabled
  144.    *          true if the Cancel button should be disabled, false otherwise
  145.    * @param   hideBackAndCancelButtons
  146.    *          true if the Cancel button should be hidden, false otherwise
  147.    * @param   extraButtonLabel
  148.    *          The label to put on the Extra button, if present, or null for 
  149.    *          default.
  150.    * @param   extraButtonDisabled
  151.    *          true if the Extra button should be disabled, false otherwise
  152.    */
  153.   setButtons: function(backButtonLabel, backButtonDisabled, 
  154.                        nextButtonLabel, nextButtonDisabled,
  155.                        finishButtonLabel, finishButtonDisabled,
  156.                        cancelButtonLabel, cancelButtonDisabled,
  157.                        hideBackAndCancelButtons,
  158.                        extraButtonLabel, extraButtonDisabled) {
  159.     var bb = this.wiz.getButton("back");
  160.     var bn = this.wiz.getButton("next");
  161.     var bf = this.wiz.getButton("finish");
  162.     var bc = this.wiz.getButton("cancel");
  163.     var be = this.wiz.getButton("extra1");
  164.  
  165.     bb.label    = backButtonLabel   || this._buttonLabel_back;
  166.     bn.label    = nextButtonLabel   || this._buttonLabel_next;
  167.     bf.label    = finishButtonLabel || this._buttonLabel_finish;
  168.     bc.label    = cancelButtonLabel || this._buttonLabel_hide;
  169.     be.label    = extraButtonLabel || this._buttonLabel_hide;
  170.     
  171.     // update button state using the wizard commands
  172.     this.wiz.canRewind  = !backButtonDisabled;
  173.     // The Finish and Next buttons are never exposed at the same time
  174.     if (this.wiz.onLastPage)
  175.       this.wiz.canAdvance = !finishButtonDisabled;
  176.     else
  177.       this.wiz.canAdvance = !nextButtonDisabled;
  178.  
  179.     bf.disabled = finishButtonDisabled;
  180.     bc.disabled = cancelButtonDisabled;
  181.     be.disabled = extraButtonDisabled;
  182.  
  183.     // Show or hide the cancel and back buttons, since the Extra 
  184.     // button does this job.
  185.     bc.hidden   = hideBackAndCancelButtons;  
  186.     bb.hidden   = hideBackAndCancelButtons;
  187.  
  188.     // Show or hide the extra button
  189.     be.hidden = extraButtonLabel == null;
  190.   },
  191.   
  192.   /**
  193.    * A hash of |pageid| attribute to page object. Can be used to dispatch
  194.    * function calls to the appropriate page. 
  195.    */
  196.   _pages: { },
  197.  
  198.   /**
  199.    * Called when the user presses the "Finish" button on the wizard, dispatches
  200.    * the function call to the selected page.
  201.    */
  202.   onWizardFinish: function() {
  203.     var pageid = document.documentElement.currentPage.pageid;
  204.     if ("onWizardFinish" in this._pages[pageid])
  205.       this._pages[pageid].onWizardFinish();
  206.   },
  207.   
  208.   /**
  209.    * Called when the user presses the "Cancel" button on the wizard, dispatches
  210.    * the function call to the selected page.
  211.    */
  212.   onWizardCancel: function() {
  213.     var pageid = document.documentElement.currentPage.pageid;
  214.     if ("onWizardCancel" in this._pages[pageid])
  215.       this._pages[pageid].onWizardCancel();
  216.   },
  217.   
  218.   /**
  219.    * Called when the user presses the "Next" button on the wizard, dispatches
  220.    * the function call to the selected page.
  221.    */
  222.   onWizardNext: function() {
  223.     var cp = document.documentElement.currentPage;
  224.     if (!cp)  
  225.       return;
  226.     var pageid = cp.pageid;
  227.     if ("onWizardNext" in this._pages[pageid])
  228.       this._pages[pageid].onWizardNext();
  229.   },
  230.   
  231.   /**
  232.    * The checking process that spawned this update UI. There are two types:
  233.    * SRCEVT_FOREGROUND:
  234.    *   Some user-generated event caused this UI to appear, e.g. the Help
  235.    *   menu item or the button in preferences. When in this mode, the UI
  236.    *   should remain active for the duration of the download. 
  237.    * SRCEVT_BACKGROUND:
  238.    *   A background update check caused this UI to appear, probably because
  239.    *   incompatibilities in Extensions or other addons were discovered and
  240.    *   the user's consent to continue was required. When in this mode, the
  241.    *   UI will disappear after the user's consent is obtained.
  242.    */
  243.   sourceEvent: SRCEVT_FOREGROUND,
  244.   
  245.   /**
  246.    * The global error message - the reason the update failed. This is human
  247.    * readable text, used to initialize the error page.
  248.    */
  249.   errorMessage: "",
  250.   
  251.   /**
  252.    * Called when the wizard UI is loaded.
  253.    */
  254.   onLoad: function() {
  255.     this.wiz = document.documentElement;
  256.     
  257.     gPref = Components.classes["@mozilla.org/preferences-service;1"]
  258.                       .getService(Components.interfaces.nsIPrefBranch2);
  259.     gConsole = Components.classes["@mozilla.org/consoleservice;1"]
  260.                          .getService(Components.interfaces.nsIConsoleService);  
  261.     this._initLoggingPrefs();
  262.  
  263.     this.strings = document.getElementById("updateStrings");
  264.     var brandStrings = document.getElementById("brandStrings");
  265.     this.brandName = brandStrings.getString("brandShortName");
  266.  
  267.     var pages = gUpdates.wiz.childNodes;
  268.     for (var i = 0; i < pages.length; ++i) {
  269.       var page = pages[i];
  270.       if (page.localName == "wizardpage") 
  271.         this._pages[page.pageid] = eval(page.getAttribute("object"));
  272.     }
  273.   
  274.     // Cache the standard button labels in case we need to restore them
  275.     this._buttonLabel_back = this.wiz.getButton("back").label;
  276.     this._buttonLabel_next = this.wiz.getButton("next").label;
  277.     this._buttonLabel_finish = this.wiz.getButton("finish").label;
  278.     this._buttonLabel_cancel = this.wiz.getButton("cancel").label;
  279.     this._buttonLabel_hide = this.strings.getString("hideButtonLabel");
  280.     this.wiz.getButton("cancel").label = this._buttonLabel_hide;
  281.     
  282.     // Advance to the Start page. 
  283.     gUpdates.wiz.currentPage = this.startPage;
  284.   },
  285.  
  286.   /**
  287.    * Initialize Logging preferences, formatted like so:
  288.    *  app.update.log.<moduleName> = <true|false>
  289.    */
  290.   _initLoggingPrefs: function() {
  291.     try {
  292.       var ps = Components.classes["@mozilla.org/preferences-service;1"]
  293.                         .getService(Components.interfaces.nsIPrefService);
  294.       var logBranch = ps.getBranch(PREF_APP_UPDATE_LOG_BRANCH);
  295.       var modules = logBranch.getChildList("", { value: 0 });
  296.  
  297.       for (var i = 0; i < modules.length; ++i) {
  298.         if (logBranch.prefHasUserValue(modules[i]))
  299.           gLogEnabled[modules[i]] = logBranch.getBoolPref(modules[i]);
  300.       }
  301.     }
  302.     catch (e) {
  303.     }
  304.   },
  305.     
  306.   /**
  307.    * Return the <wizardpage> object that should be displayed first.
  308.    *
  309.    * This is determined by how we were called by the update prompt:
  310.    *
  311.    * U'Prompt Method:     Arg0:         Update State: Src Event:  p'Failed: Result:
  312.    * showUpdateAvailable  nsIUpdate obj --            background  --        updatesfound
  313.    * showUpdateDownloaded nsIUpdate obj pending       background  --        finishedBackground
  314.    * showUpdateInstalled  nsIUpdate obj succeeded     either      --        installed
  315.    * showUpdateError      nsIUpdate obj failed        either      partial   errorpatching
  316.    * showUpdateError      nsIUpdate obj failed        either      complete  errors
  317.    * checkForUpdates      null          --            foreground  --        checking
  318.    * checkForUpdates      null          downloading   foreground  --        downloading
  319.    */
  320.   get startPage() {
  321.     if (window.arguments) {
  322.       var arg0 = window.arguments[0];
  323.       if (arg0 instanceof Components.interfaces.nsIUpdate) {
  324.         // If the first argument is a nsIUpdate object, we are notifying the
  325.         // user that the background checking found an update that requires
  326.         // their permission to install, and it's ready for download.
  327.         this.setUpdate(arg0);
  328.         var p = this.update.selectedPatch;
  329.         if (p) {
  330.           var state = p.state;
  331.           if (state == STATE_DOWNLOADING) {
  332.             var patchFailed = false;
  333.             try {
  334.               patchFailed = this.update.getProperty("patchingFailed");
  335.             }
  336.             catch (e) {
  337.             }
  338.             if (patchFailed == "partial") {
  339.               // If the system failed to apply the partial patch, show the 
  340.               // screen which best describes this condition, which is triggered
  341.               // by the |STATE_FAILED| state.
  342.               state = STATE_FAILED; 
  343.             }
  344.             else if (patchFailed == "complete") {
  345.               // Otherwise, if the complete patch failed, which is far less 
  346.               // likely, show the error text held by the update object in the
  347.               // generic errors page, triggered by the |STATE_DOWNLOAD_FAILED|
  348.               // state.
  349.               state = STATE_DOWNLOAD_FAILED;
  350.             }
  351.           }
  352.           
  353.           // Now select the best page to start with, given the current state of
  354.           // the Update.
  355.           switch (state) {
  356.           case STATE_PENDING:
  357.             this.sourceEvent = SRCEVT_BACKGROUND;
  358.             return document.getElementById("finishedBackground");
  359.           case STATE_SUCCEEDED:
  360.             return document.getElementById("installed");
  361.           case STATE_DOWNLOADING:
  362.             return document.getElementById("downloading");
  363.           case STATE_FAILED:
  364.             window.getAttention();            
  365.             return document.getElementById("errorpatching");
  366.           case STATE_DOWNLOAD_FAILED:
  367.           case STATE_APPLYING:
  368.             return document.getElementById("errors");
  369.           }
  370.         }
  371.         return document.getElementById("updatesfound");
  372.       }
  373.     }
  374.     else {
  375.       var um = 
  376.           Components.classes["@mozilla.org/updates/update-manager;1"].
  377.           getService(Components.interfaces.nsIUpdateManager);
  378.       if (um.activeUpdate) {
  379.         this.setUpdate(um.activeUpdate);
  380.         return document.getElementById("downloading");
  381.       }
  382.     }
  383.     return document.getElementById("checking");
  384.   },
  385.   
  386.   /**
  387.    * Sets the Update object for this wizard
  388.    * @param   update
  389.    *          The update object 
  390.    */
  391.   setUpdate: function(update) {
  392.     this.update = update;
  393.     if (this.update)
  394.       this.update.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
  395.   },
  396.   
  397.   /**
  398.    * Registers a timer to nag the user about something relating to update
  399.    * @param   timerID
  400.    *          The ID of the timer to register, used for persistence
  401.    * @param   timerInterval
  402.    *          The interval of the timer
  403.    * @param   methodName
  404.    *          The method to call on the Update Prompter when the timer fires
  405.    */
  406.   registerNagTimer: function(timerID, timerInterval, methodName) {
  407.     // Remind the user to restart their browser in a little bit.
  408.     var tm = 
  409.         Components.classes["@mozilla.org/updates/timer-manager;1"].
  410.         getService(Components.interfaces.nsIUpdateTimerManager);
  411.     
  412.     /**
  413.      * An object implementing nsITimerCallback that uses the Update Prompt
  414.      * component to notify the user about some event relating to app update
  415.      * that they should take action on.
  416.      * @param   update
  417.      *          The nsIUpdate object in question
  418.      * @param   methodName
  419.      *          The name of the method on the Update Prompter that should be 
  420.      *          called
  421.      * @constructor
  422.      */
  423.     function Callback(update, methodName) {
  424.       this._update = update;
  425.       this._methodName = methodName;
  426.       this._prompter = 
  427.         Components.classes["@mozilla.org/updates/update-prompt;1"].
  428.         createInstance(Components.interfaces.nsIUpdatePrompt);      
  429.     }
  430.     Callback.prototype = {
  431.       /**
  432.        * The Update we should nag about downloading
  433.        */
  434.       _update: null,
  435.       
  436.       /**
  437.        * The Update prompter we can use to notify the user
  438.        */
  439.       _prompter: null,
  440.       
  441.       /**
  442.        * The method on the update prompt that should be called
  443.        */
  444.       _methodName: "",
  445.       
  446.       /**
  447.        * Called when the timer fires. Notifies the user about whichever event 
  448.        * they need to be nagged about (e.g. update available, please restart,
  449.        * etc).
  450.        */
  451.       notify: function(timerCallback) {
  452.         if (methodName in this._prompter) 
  453.           this._prompter[methodName](null, this._update);
  454.       }
  455.     }
  456.     tm.registerTimer(timerID, (new Callback(gUpdates.update, methodName)), 
  457.                      timerInterval);
  458.   },
  459. }
  460.  
  461. /**
  462.  * The "Checking for Updates" page. Provides feedback on the update checking
  463.  * process.
  464.  */
  465. var gCheckingPage = {
  466.   /**
  467.    * The nsIUpdateChecker that is currently checking for updates. We hold onto 
  468.    * this so we can cancel the update check if the user closes the window.
  469.    */
  470.   _checker: null,
  471.   
  472.   /**
  473.    * Starts the update check when the page is shown.
  474.    */
  475.   onPageShow: function() {
  476.     gUpdates.setButtons(null, true, null, true, null, true, 
  477.                         gUpdates._buttonLabel_cancel, false, false, null, 
  478.                         false);
  479.     this._checker = 
  480.       Components.classes["@mozilla.org/updates/update-checker;1"].
  481.       createInstance(Components.interfaces.nsIUpdateChecker);
  482.     this._checker.checkForUpdates(this.updateListener, true);
  483.   },
  484.   
  485.   /**
  486.    * The user has closed the window, either by pressing cancel or using a Window
  487.    * Manager control, so stop checking for updates.
  488.    */
  489.   onWizardCancel: function() {
  490.     if (this._checker) {
  491.       const nsIUpdateChecker = Components.interfaces.nsIUpdateChecker;
  492.       this._checker.stopChecking(nsIUpdateChecker.CURRENT_CHECK);
  493.     }
  494.   },
  495.   
  496.   /**
  497.    * An object implementing nsIUpdateCheckListener that is notified as the
  498.    * update check commences.
  499.    */
  500.   updateListener: {
  501.     /**
  502.      * See nsIUpdateCheckListener
  503.      */
  504.     onProgress: function(request, position, totalSize) {
  505.       var pm = document.getElementById("checkingProgress");
  506.       checkingProgress.setAttribute("mode", "normal");
  507.       checkingProgress.setAttribute("value", Math.floor(100 * (position/totalSize)));
  508.     },
  509.  
  510.     /**
  511.      * See nsIUpdateCheckListener
  512.      */
  513.     onCheckComplete: function(request, updates, updateCount) {
  514.       var aus = Components.classes["@mozilla.org/updates/update-service;1"]
  515.                           .getService(Components.interfaces.nsIApplicationUpdateService);
  516.       gUpdates.setUpdate(aus.selectUpdate(updates, updates.length));
  517.       if (!gUpdates.update) {
  518.         LOG("UI:CheckingPage", 
  519.             "Could not select an appropriate update, either because there " + 
  520.             "were none, or |selectUpdate| failed.");
  521.         var checking = document.getElementById("checking");
  522.         checking.setAttribute("next", "noupdatesfound");
  523.       }
  524.       gUpdates.wiz.canAdvance = true;
  525.       gUpdates.wiz.advance();
  526.     },
  527.  
  528.     /**
  529.      * See nsIUpdateCheckListener
  530.      */
  531.     onError: function(request, update) {
  532.       LOG("UI:CheckingPage", "UpdateCheckListener: error");
  533.  
  534.       gUpdates.setUpdate(update);
  535.  
  536.       gUpdates.wiz.currentPage = document.getElementById("errors");
  537.     },
  538.     
  539.     /**
  540.      * See nsISupports.idl
  541.      */
  542.     QueryInterface: function(iid) {
  543.       if (!aIID.equals(Components.interfaces.nsIUpdateCheckListener) &&
  544.           !aIID.equals(Components.interfaces.nsISupports))
  545.         throw Components.results.NS_ERROR_NO_INTERFACE;
  546.       return this;
  547.     }
  548.   }
  549. };
  550.  
  551. /**
  552.  * The "No Updates Are Available" page
  553.  */
  554. var gNoUpdatesPage = {
  555.   /**
  556.    * Initialize
  557.    */
  558.   onPageShow: function() {
  559.     gUpdates.setButtons(null, true, null, true, null, false, null, true, false,
  560.                         null, false);
  561.     gUpdates.wiz.getButton("finish").focus();
  562.   }
  563. };
  564.  
  565. /**
  566.  * The "Updates Are Available" page. Provides the user information about the
  567.  * available update, extensions it might make incompatible, and a means to 
  568.  * continue downloading and installing it.
  569.  */
  570. var gUpdatesAvailablePage = {
  571.   /**
  572.    * An array of installed addons incompatible with this update.
  573.    */
  574.   _incompatibleItems: null,
  575.   
  576.   /**
  577.    * Initialize.
  578.    */
  579.   onPageShow: function() {
  580.     var updateName = gUpdates.strings.getFormattedString("updateName", 
  581.       [gUpdates.brandName, gUpdates.update.version]);
  582.     var updateNameElement = document.getElementById("updateName");
  583.     updateNameElement.value = updateName;
  584.     var severity = gUpdates.update.isSecurityUpdate ? "minor" : "major";
  585.     var displayType = gUpdates.strings.getString("updateType_" + severity);
  586.     var updateTypeElement = document.getElementById("updateType");
  587.     updateTypeElement.setAttribute("severity", severity);
  588.     var intro = gUpdates.strings.getFormattedString(
  589.       "introType_" + severity, [gUpdates.brandName]);
  590.     while (updateTypeElement.hasChildNodes())
  591.       updateTypeElement.removeChild(updateTypeElement.firstChild);
  592.     updateTypeElement.appendChild(document.createTextNode(intro));
  593.     
  594.     var updateMoreInfoURL = document.getElementById("updateMoreInfoURL");
  595.     updateMoreInfoURL.href = gUpdates.update.detailsURL;
  596.     
  597.     var em = Components.classes["@mozilla.org/extensions/manager;1"]
  598.                        .getService(Components.interfaces.nsIExtensionManager);
  599.     var items = em.getIncompatibleItemList("", gUpdates.update.version,
  600.                                            nsIUpdateItem.TYPE_ADDON, false, 
  601.                                            { });
  602.     if (items.length > 0) {
  603.       // There are addons that are incompatible with this update, so show the 
  604.       // warning message.
  605.       var incompatibleWarning = document.getElementById("incompatibleWarning");
  606.       incompatibleWarning.hidden = false;
  607.       
  608.       this._incompatibleItems = items;
  609.     }
  610.     
  611.     // If we were invoked from a background update check, automatically show 
  612.     // the additional details the user may need to make this decision since 
  613.     // they did not consciously make the decision to check. 
  614.     // if (gUpdates.sourceEvent == SRCEVT_BACKGROUND)
  615.     // This is ridiculous... always show the additional info.
  616.     this.onShowMoreDetails();
  617.       
  618.     try {
  619.       gUpdates.update.getProperty("licenseAccepted");
  620.     }
  621.     catch (e) {
  622.       gUpdates.update.setProperty("licenseAccepted", "false");
  623.     }
  624.  
  625.     var downloadNowLabel = gUpdates.wiz.currentPage.getAttribute("downloadNowLabel");
  626.     var downloadLaterLabel = gUpdates.wiz.currentPage.getAttribute("downloadLaterLabel");
  627.     gUpdates.setButtons(null, false, downloadNowLabel, false, null, false,
  628.                         null, false, true, 
  629.                         downloadLaterLabel, false);
  630.     gUpdates.wiz.getButton("next").focus();
  631.   },
  632.   
  633.   /**
  634.    * User clicked the "More Details..." button
  635.    */
  636.   onShowMoreDetails: function() {
  637.     var detailsDeck = document.getElementById("detailsDeck");
  638.     detailsDeck.selectedIndex = 1;
  639.   },
  640.   
  641.   /**
  642.    * User said they wanted to install now, so advance to the License or 
  643.    * Downloading page, whichever is appropriate.
  644.    */
  645.   onInstallNow: function() {
  646.     var nextPageID = "downloading";
  647.     if (gUpdates.update.licenseURL) {
  648.       try {
  649.         var licenseAccepted = gUpdates.update.getProperty("licenseAccepted");
  650.       }
  651.       catch(e) {
  652.         // |getProperty| throws NS_ERROR_FAILURE if the property is not
  653.         // defined on the property bag.
  654.       }
  655.       if (licenseAccepted != "true")
  656.         nextPageID = "license";
  657.     }
  658.     gUpdates.wiz.currentPage = document.getElementById(nextPageID);
  659.   },
  660.   
  661.   /**
  662.    * User said that they would install later. Register a timer to remind them
  663.    * after a day or so.
  664.    */
  665.   onInstallLater: function() {
  666.     var interval = getPref("getIntPref", PREF_UPDATE_NAGTIMER_DL, 86400000);
  667.     gUpdates.registerNagTimer("download-nag-timer", interval, 
  668.                               "showUpdateAvailable");
  669.  
  670.     // The user said "Later", so stop all update checks for this session
  671.     // so that we don't bother them again.
  672.     const nsIUpdateChecker = Components.interfaces.nsIUpdateChecker;
  673.     var aus = 
  674.         Components.classes["@mozilla.org/updates/update-service;1"].
  675.         getService(Components.interfaces.nsIApplicationUpdateService);
  676.     aus.backgroundChecker.stopChecking(nsIUpdateChecker.CURRENT_SESSION);
  677.  
  678.     close();
  679.   },
  680.   
  681.   /** 
  682.    * Show a list of extensions made incompatible by this update.
  683.    */
  684.   showIncompatibleItems: function() {
  685.     openDialog("chrome://mozapps/content/update/incompatible.xul", "", 
  686.                "dialog,centerscreen,modal,resizable,titlebar", this._incompatibleItems);
  687.   }
  688. };
  689.  
  690. /**
  691.  * The page which shows the user a license associated with an update. The
  692.  * user must agree to the terms of the license before continuing to install
  693.  * the update.
  694.  */
  695. var gLicensePage = {
  696.   /**
  697.    * The <license> element
  698.    */
  699.   _licenseContent: null,
  700.   
  701.   /**
  702.    * Initialize
  703.    */
  704.   onPageShow: function() {
  705.     this._licenseContent = document.getElementById("licenseContent");
  706.     
  707.     var IAgree = gUpdates.strings.getString("IAgreeLabel");
  708.     var IDoNotAgree = gUpdates.strings.getString("IDoNotAgreeLabel");
  709.     gUpdates.setButtons(null, true, IAgree, true, null, true, IDoNotAgree, 
  710.                         false, false, null, false);
  711.  
  712.     this._licenseContent.addEventListener("load", this.onLicenseLoad, false);
  713.     this._licenseContent.url = gUpdates.update.licenseURL;
  714.   },
  715.   
  716.   /**
  717.    * When the license document has loaded
  718.    */
  719.   onLicenseLoad: function() {
  720.     // Now that the license text is available, the user is in a position to
  721.     // agree to it, so enable the Agree button.
  722.     gUpdates.wiz.getButton("next").disabled = false;
  723.     gUpdates.wiz.getButton("next").focus();
  724.   },
  725.   
  726.   /**
  727.    * When the user accepts the license
  728.    */
  729.   onWizardNext: function() {
  730.     gUpdates.update.setProperty("licenseAccepted", "true");
  731.     var um = 
  732.         Components.classes["@mozilla.org/updates/update-manager;1"].
  733.         getService(Components.interfaces.nsIUpdateManager);
  734.     um.saveUpdates();
  735.   },
  736.   
  737.   /**
  738.    * When the user cancels the wizard
  739.    */
  740.   onWizardCancel: function() {
  741.     // If the license was downloading, stop it.
  742.     this._licenseContent.stopDownloading();
  743.     
  744.     // The user said "Do Not Agree", so stop all update checks for this session
  745.     // so that we don't bother them again.
  746.     const nsIUpdateChecker = Components.interfaces.nsIUpdateChecker;
  747.     var aus = 
  748.         Components.classes["@mozilla.org/updates/update-service;1"].
  749.         getService(Components.interfaces.nsIApplicationUpdateService);
  750.     aus.backgroundChecker.stopChecking(nsIUpdateChecker.CURRENT_SESSION);
  751.   }
  752. };
  753.  
  754. /**
  755.  * Formats status messages for a download operation based on the progress
  756.  * of the download.
  757.  * @constructor
  758.  */
  759. function DownloadStatusFormatter() {
  760.   this._startTime = Math.floor((new Date()).getTime() / 1000);
  761.   this._elapsed = 0;
  762.   
  763.   var us = gUpdates.strings;
  764.   this._statusFormat = us.getString("statusFormat");
  765.  
  766.   this._progressFormat = us.getString("progressFormat");
  767.   this._progressFormatKBMB = us.getString("progressFormatKBMB");
  768.   this._progressFormatKBKB = us.getString("progressFormatKBKB");
  769.   this._progressFormatMBMB = us.getString("progressFormatMBMB");
  770.   this._progressFormatUnknownMB = us.getString("progressFormatUnknownMB");
  771.   this._progressFormatUnknownKB = us.getString("progressFormatUnknownKB");
  772.  
  773.   this._rateFormat = us.getString("rateFormat");
  774.   this._rateFormatKBSec = us.getString("rateFormatKBSec");
  775.   this._rateFormatMBSec = us.getString("rateFormatMBSec");
  776.  
  777.   this._timeFormat = us.getString("timeFormat");
  778.   this._longTimeFormat = us.getString("longTimeFormat");
  779.   this._shortTimeFormat = us.getString("shortTimeFormat");
  780.  
  781.   this._remain = us.getString("remain");
  782.   this._unknownFilesize = us.getString("unknownFilesize");
  783. }
  784. DownloadStatusFormatter.prototype = {
  785.   /**
  786.    * Time when the download started (in seconds since epoch)
  787.    */
  788.   _startTime: 0,
  789.  
  790.   /**
  791.    * Time elapsed since the start of the download operation (in seconds)
  792.    */
  793.   _elapsed: -1,
  794.   
  795.   /**
  796.    * Transfer rate of the download
  797.    */
  798.   _rate: 0,
  799.   
  800.   /**
  801.    * Transfer rate of the download, formatted as text
  802.    */
  803.   _rateFormatted: "",
  804.   
  805.   /**
  806.    * Transfer rate, formatted into text container
  807.    */
  808.   _rateFormattedContainer: "",
  809.   
  810.   /**
  811.    * Number of Kilobytes downloaded so far in the form:
  812.    *  376KB of 9.3MB
  813.    */
  814.   progress: "",
  815.  
  816.   /**
  817.    * Format a human-readable status message based on the current download
  818.    * progress.
  819.    * @param   currSize
  820.    *          The current number of bytes transferred
  821.    * @param   finalSize
  822.    *          The total number of bytes to be transferred
  823.    * @returns A human readable status message, e.g.
  824.    *          "3.4 of 4.7MB; 01:15 remain"
  825.    */
  826.   formatStatus: function(currSize, finalSize) {
  827.     var now = Math.floor((new Date()).getTime() / 1000);
  828.     
  829.     // 1) Determine the Download Progress in Kilobytes
  830.     var total = parseInt(finalSize/1024 + 0.5);
  831.     this.progress = this._formatKBytes(parseInt(currSize/1024 + 0.5), total);
  832.     
  833.     var progress = this._replaceInsert(this._progressFormat, 1, this.progress);
  834.     var rateFormatted = "";
  835.     
  836.     // 2) Determine the Transfer Rate
  837.     var oldElapsed = this._elapsed;
  838.     this._elapsed = now - this._startTime;
  839.     if (oldElapsed != this._elapsed) {
  840.       this._rate = this._elapsed ? Math.floor((currSize / 1024) / this._elapsed) : 0;
  841.       var isKB = true;
  842.       if (parseInt(this._rate / 1024) > 0) {
  843.         this._rate = (this._rate / 1024).toFixed(1);
  844.         isKB = false;
  845.       }
  846.       if (this._rate > 100)
  847.         this._rate = Math.round(this._rate);
  848.       
  849.       if (this._rate) {
  850.         var format = isKB ? this._rateFormatKBSec : this._rateFormatMBSec;
  851.         this._rateFormatted = this._replaceInsert(format, 1, this._rate);
  852.         this._rateFormattedContainer = this._replaceInsert(" " + this._rateFormat, 1, this._rateFormatted);
  853.       }
  854.     }
  855.     progress = this._replaceInsert(progress, 2, this._rateFormattedContainer);
  856.     
  857.  
  858.     // 3) Determine the Time Remaining
  859.     var remainingTime = "";
  860.     if (this._rate && (finalSize > 0)) {
  861.       remainingTime = Math.floor(((finalSize - currSize) / 1024) / this._rate);
  862.       remainingTime = this._formatSeconds(remainingTime);
  863.       remainingTime = this._replaceInsert(this._timeFormat, 1, remainingTime)
  864.       remainingTime = this._replaceInsert(remainingTime, 2, this._remain);
  865.     }
  866.     
  867.     //
  868.     // [statusFormat:
  869.     //  [progressFormat:
  870.     //   [[progressFormatKBKB|
  871.     //     progressFormatKBMB|
  872.     //     progressFormatMBMB|
  873.     //     progressFormatUnknownKB|
  874.     //     progressFormatUnknownMB
  875.     //    ][rateFormat]]
  876.     //  ][timeFormat]
  877.     // ]
  878.     var status = this._statusFormat;
  879.     status = this._replaceInsert(status, 1, progress);
  880.     status = this._replaceInsert(status, 2, remainingTime);
  881.     return status;
  882.   },
  883.  
  884.   /**
  885.    * Inserts a string into another string at the specified index, e.g. for
  886.    * the format string var foo ="#1 #2 #3", |_replaceInsert(foo, 2, "test")|
  887.    * returns "#1 test #3";
  888.    * @param   format
  889.    *          The format string
  890.    * @param   index
  891.    *          The Index to insert into
  892.    * @param   value
  893.    *          The value to insert
  894.    * @returns The string with the value inserted. 
  895.    */  
  896.   _replaceInsert: function(format, index, value) {
  897.     return format.replace(new RegExp("#" + index), value);
  898.   },
  899.  
  900.   /**
  901.    * Formats progress in the form of kilobytes transfered vs. total to 
  902.    * transfer.
  903.    * @param   currentKB
  904.    *          The current amount of data transfered, in kilobytes.
  905.    * @param   totalKB
  906.    *          The total amount of data that must be transfered, in kilobytes.
  907.    * @returns A string representation of the progress, formatted according to:
  908.    * 
  909.    *            KB           totalKB           returns
  910.    *            x, < 1MB     y < 1MB           x of y KB
  911.    *            x, < 1MB     y >= 1MB          x KB of y MB
  912.    *            x, >= 1MB    y >= 1MB          x of y MB
  913.    */
  914.   _formatKBytes: function(currentKB, totalKB) {
  915.     var progressHasMB = parseInt(currentKB / 1024) > 0;
  916.     var totalHasMB = parseInt(totalKB / 1024) > 0;
  917.     
  918.     var format = "";
  919.     if (!progressHasMB && !totalHasMB) {
  920.       if (!totalKB) {
  921.         format = this._progressFormatUnknownKB;
  922.         format = this._replaceInsert(format, 1, currentKB);
  923.       } else {
  924.         format = this._progressFormatKBKB;
  925.         format = this._replaceInsert(format, 1, currentKB);
  926.         format = this._replaceInsert(format, 2, totalKB);
  927.       }
  928.     }
  929.     else if (progressHasMB && totalHasMB) {
  930.       format = this._progressFormatMBMB;
  931.       format = this._replaceInsert(format, 1, (currentKB / 1024).toFixed(1));
  932.       format = this._replaceInsert(format, 2, (totalKB / 1024).toFixed(1));
  933.     }
  934.     else if (totalHasMB && !progressHasMB) {
  935.       format = this._progressFormatKBMB;
  936.       format = this._replaceInsert(format, 1, currentKB);
  937.       format = this._replaceInsert(format, 2, (totalKB / 1024).toFixed(1));
  938.     }
  939.     else if (progressHasMB && !totalHasMB) {
  940.       format = this._progressFormatUnknownMB;
  941.       format = this._replaceInsert(format, 1, (currentKB / 1024).toFixed(1));
  942.     }
  943.     return format;  
  944.   },
  945.  
  946.   /**
  947.    * Formats a time in seconds into something human readable.
  948.    * @param   seconds
  949.    *          The time to format
  950.    * @returns A human readable string representing the date.
  951.    */
  952.   _formatSeconds: function(seconds) {
  953.     // Determine number of hours/minutes/seconds
  954.     var hours = (seconds - (seconds % 3600)) / 3600;
  955.     seconds -= hours * 3600;
  956.     var minutes = (seconds - (seconds % 60)) / 60;
  957.     seconds -= minutes * 60;
  958.     
  959.     // Pad single digit values
  960.     if (hours < 10)
  961.       hours = "0" + hours;
  962.     if (minutes < 10)
  963.       minutes = "0" + minutes;
  964.     if (seconds < 10)
  965.       seconds = "0" + seconds;
  966.     
  967.     // Insert hours, minutes, and seconds into result string.
  968.     var result = parseInt(hours) ? this._longTimeFormat : this._shortTimeFormat;
  969.     result = this._replaceInsert(result, 1, hours);
  970.     result = this._replaceInsert(result, 2, minutes);
  971.     result = this._replaceInsert(result, 3, seconds);
  972.  
  973.     return result;
  974.   }
  975. };
  976.  
  977. /**
  978.  * The "Update is Downloading" page - provides feedback for the download 
  979.  * process plus a pause/resume UI
  980.  */
  981. var gDownloadingPage = {
  982.   /** 
  983.    * DOM Elements
  984.    */
  985.   _downloadName     : null,
  986.   _downloadStatus   : null,
  987.   _downloadProgress : null,
  988.   _downloadThrobber : null,
  989.   _pauseButton      : null,
  990.   
  991.   /**
  992.    * Label cache to hold the 'Connecting' string
  993.    */
  994.   _label_downloadStatus : null,
  995.  
  996.   /** 
  997.    * An instance of the status formatter object
  998.    */
  999.   _statusFormatter  : null,
  1000.   get statusFormatter() {
  1001.     if (!this._statusFormatter) 
  1002.       this._statusFormatter = new DownloadStatusFormatter();
  1003.     return this._statusFormatter;
  1004.   },
  1005.   
  1006.   /** 
  1007.    * Initialize
  1008.    */
  1009.   onPageShow: function() {
  1010.     this._downloadName = document.getElementById("downloadName");
  1011.     this._downloadStatus = document.getElementById("downloadStatus");
  1012.     this._downloadProgress = document.getElementById("downloadProgress");
  1013.     this._downloadThrobber = document.getElementById("downloadThrobber");
  1014.     this._pauseButton = document.getElementById("pauseButton");
  1015.     this._label_downloadStatus = this._downloadStatus.textContent;
  1016.   
  1017.     var updates = 
  1018.         Components.classes["@mozilla.org/updates/update-service;1"].
  1019.         getService(Components.interfaces.nsIApplicationUpdateService);
  1020.  
  1021.     var um = 
  1022.         Components.classes["@mozilla.org/updates/update-manager;1"].
  1023.         getService(Components.interfaces.nsIUpdateManager);
  1024.     var activeUpdate = um.activeUpdate;
  1025.     if (activeUpdate)
  1026.       gUpdates.setUpdate(activeUpdate);
  1027.     
  1028.     if (!gUpdates.update) {
  1029.       LOG("UI:DownloadingPage", "onPageShow: no valid update to download?!");
  1030.       return;
  1031.     }
  1032.     
  1033.     // Say that this was a foreground download, not a background download, 
  1034.     // since the user cared enough to look in on this process.
  1035.     gUpdates.update.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
  1036.     gUpdates.update.setProperty("foregroundDownload", "true");
  1037.   
  1038.     // Pause any active background download and restart it as a foreground
  1039.     // download.
  1040.     updates.pauseDownload();
  1041.     var state = updates.downloadUpdate(gUpdates.update, false);
  1042.     if (state == "failed") {
  1043.       // We've tried as hard as we could to download a valid update - 
  1044.       // we fell back from a partial patch to a complete patch and even
  1045.       // then we couldn't validate. Show a validation error with instructions
  1046.       // on how to manually update.
  1047.       this.showVerificationError();
  1048.     }
  1049.     else {
  1050.       // Add this UI as a listener for active downloads
  1051.       updates.addDownloadListener(this);
  1052.     }
  1053.     
  1054.     if (activeUpdate)
  1055.       this._setUIState(!updates.isDownloading);
  1056.  
  1057.     var link = document.getElementById("detailsLink");
  1058.     link.href = gUpdates.update.detailsURL;
  1059.  
  1060.     gUpdates.setButtons(null, true, null, true, null, true, null, false, false,
  1061.                         null, false);
  1062.   },
  1063.   
  1064.   /** 
  1065.    * Updates the text status message
  1066.    */
  1067.   _setStatus: function(status) {
  1068.     // Don't bother setting the same text more than once. This can happen
  1069.     // due to the asynchronous behavior of the downloader.
  1070.     if (this._downloadStatus.textContent == status)
  1071.       return;
  1072.     while (this._downloadStatus.hasChildNodes())
  1073.       this._downloadStatus.removeChild(this._downloadStatus.firstChild);
  1074.     this._downloadStatus.appendChild(document.createTextNode(status));
  1075.   },
  1076.   
  1077.   /**
  1078.    * Whether or not we are currently paused
  1079.    */
  1080.   _paused       : false,
  1081.   
  1082.   /**
  1083.    * Adjust UI to suit a certain state of paused-ness
  1084.    * @param   paused
  1085.    *          Whether or not the download is paused
  1086.    */
  1087.   _setUIState: function(paused) {
  1088.     var u = gUpdates.update;
  1089.     if (paused) {
  1090.       if (this._downloadThrobber.hasAttribute("state"))
  1091.         this._downloadThrobber.removeAttribute("state");
  1092.       if (this._downloadProgress.mode != "normal")
  1093.         this._downloadProgress.mode = "normal";
  1094.       this._downloadName.value = gUpdates.strings.getFormattedString(
  1095.         "pausedName", [u.name]);
  1096.       this._pauseButton.label = gUpdates.strings.getString("pauseButtonResume");
  1097.       var p = u.selectedPatch.QueryInterface(Components.interfaces.nsIPropertyBag);
  1098.       var status = p.getProperty("status");
  1099.       if (status)
  1100.         this._setStatus(status);
  1101.     }
  1102.     else {
  1103.       if (!(this._downloadThrobber.hasAttribute("state") &&
  1104.            (this._downloadThrobber.getAttribute("state") == "loading")))
  1105.         this._downloadThrobber.setAttribute("state", "loading");
  1106.       if (this._downloadProgress.mode != "undetermined")
  1107.         this._downloadProgress.mode = "undetermined";
  1108.       this._downloadName.value = gUpdates.strings.getFormattedString(
  1109.         "downloadingPrefix", [u.name]);
  1110.       this._pauseButton.label = gUpdates.strings.getString("pauseButtonPause");
  1111.       this._setStatus(this._label_downloadStatus);
  1112.     }
  1113.   },
  1114.  
  1115.   /** 
  1116.    * When the user clicks the Pause/Resume button
  1117.    */
  1118.   onPause: function() {
  1119.     var updates = 
  1120.         Components.classes["@mozilla.org/updates/update-service;1"].
  1121.         getService(Components.interfaces.nsIApplicationUpdateService);
  1122.     if (this._paused)
  1123.       updates.downloadUpdate(gUpdates.update, false);
  1124.     else {
  1125.       var patch = gUpdates.update.selectedPatch;
  1126.       patch.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
  1127.       patch.setProperty("status",
  1128.         gUpdates.strings.getFormattedString("pausedStatus", 
  1129.         [this.statusFormatter.progress]));
  1130.       updates.pauseDownload();
  1131.     }
  1132.     this._paused = !this._paused;
  1133.     
  1134.     // Update the UI
  1135.     this._setUIState(this._paused);
  1136.   },
  1137.   
  1138.   /** 
  1139.    * When the user closes the Wizard UI
  1140.    */
  1141.   onWizardCancel: function() {
  1142.     // Remove ourself as a download listener so that we don't continue to be 
  1143.     // fed progress and state notifications after the UI we're updating has 
  1144.     // gone away.
  1145.     var updates = 
  1146.         Components.classes["@mozilla.org/updates/update-service;1"].
  1147.         getService(Components.interfaces.nsIApplicationUpdateService);
  1148.     updates.removeDownloadListener(this);
  1149.     
  1150.     var um = 
  1151.         Components.classes["@mozilla.org/updates/update-manager;1"]
  1152.                   .getService(Components.interfaces.nsIUpdateManager);
  1153.     um.activeUpdate = gUpdates.update;
  1154.     
  1155.     // If the download was paused by the user, ask the user if they want to 
  1156.     // have the update resume in the background. 
  1157.     var downloadInBackground = true;
  1158.     if (this._paused) {
  1159.       var title = gUpdates.strings.getString("resumePausedAfterCloseTitle");
  1160.       var message = gUpdates.strings.getFormattedString(
  1161.         "resumePausedAfterCloseMessage", [gUpdates.brandName]);
  1162.       var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  1163.                         .getService(Components.interfaces.nsIPromptService);
  1164.       var flags = ps.STD_YES_NO_BUTTONS;
  1165.       var rv = ps.confirmEx(window, title, message, flags, null, null, null, null, { });
  1166.       if (rv == 1) {
  1167.         downloadInBackground = false;
  1168.       }
  1169.     }
  1170.     if (downloadInBackground) {
  1171.       // Cancel the download and start it again in the background.
  1172.       LOG("UI:DownloadingPage", "onWizardCancel: resuming download in background");
  1173.       updates.pauseDownload();
  1174.       updates.downloadUpdate(gUpdates.update, true);
  1175.     }
  1176.   },
  1177.   
  1178.   /** 
  1179.    * When the data transfer begins
  1180.    * @param   request
  1181.    *          The nsIRequest object for the transfer
  1182.    * @param   context
  1183.    *          Additional data
  1184.    */
  1185.   onStartRequest: function(request, context) {
  1186.     request.QueryInterface(nsIIncrementalDownload);
  1187.     LOG("UI:DownloadingPage", "onStartRequest: " + request.URI.spec);
  1188.     
  1189.     // This !paused test is necessary because onStartRequest may fire after
  1190.     // the download was paused (for those speedy clickers...)
  1191.     if (this._paused)
  1192.       return;
  1193.       
  1194.     if (!(this._downloadThrobber.hasAttribute("state") &&
  1195.           (this._downloadThrobber.getAttribute("state") == "loading")))
  1196.       this._downloadThrobber.setAttribute("state", "loading");
  1197.     if (this._downloadProgress.mode != "undetermined")
  1198.       this._downloadProgress.mode = "undetermined";
  1199.     this._setStatus(this._label_downloadStatus);
  1200.   },
  1201.   
  1202.   /** 
  1203.    * When new data has been downloaded
  1204.    * @param   request
  1205.    *          The nsIRequest object for the transfer
  1206.    * @param   context
  1207.    *          Additional data
  1208.    * @param   progress
  1209.    *          The current number of bytes transferred
  1210.    * @param   maxProgress
  1211.    *          The total number of bytes that must be transferred
  1212.    */
  1213.   onProgress: function(request, context, progress, maxProgress) {
  1214.     request.QueryInterface(nsIIncrementalDownload);
  1215.     LOG("UI:DownloadingPage.onProgress", " " + request.URI.spec + ", " + progress + 
  1216.         "/" + maxProgress);
  1217.  
  1218.     var name = gUpdates.strings.getFormattedString("downloadingPrefix", [gUpdates.update.name]);
  1219.     var status = this.statusFormatter.formatStatus(progress, maxProgress);
  1220.     var progress = Math.round(100 * (progress/maxProgress));
  1221.  
  1222.     var p = gUpdates.update.selectedPatch;
  1223.     p.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
  1224.     p.setProperty("progress", progress);
  1225.     p.setProperty("status", status);
  1226.     
  1227.     // This !paused test is necessary because onProgress may fire after
  1228.     // the download was paused (for those speedy clickers...)
  1229.     if (this._paused)
  1230.       return;
  1231.  
  1232.     if (!(this._downloadThrobber.hasAttribute("state") &&
  1233.          (this._downloadThrobber.getAttribute("state") == "loading")))
  1234.       this._downloadThrobber.setAttribute("state", "loading");
  1235.     if (this._downloadProgress.mode != "normal")
  1236.       this._downloadProgress.mode = "normal";
  1237.     this._downloadProgress.value = progress;
  1238.     this._pauseButton.disabled = false;
  1239.     this._downloadName.value = name;
  1240.     this._setStatus(status);
  1241.   },
  1242.   
  1243.   /** 
  1244.    * When we have new status text
  1245.    * @param   request
  1246.    *          The nsIRequest object for the transfer
  1247.    * @param   context
  1248.    *          Additional data
  1249.    * @param   status
  1250.    *          A status code
  1251.    * @param   statusText
  1252.    *          Human readable version of |status|
  1253.    */
  1254.   onStatus: function(request, context, status, statusText) {
  1255.     request.QueryInterface(nsIIncrementalDownload);
  1256.     LOG("UI:DownloadingPage", "onStatus: " + request.URI.spec + " status = " + 
  1257.         status + ", text = " + statusText);
  1258.     this._setStatus(statusText);
  1259.   },
  1260.   
  1261.   /** 
  1262.    * When data transfer ceases
  1263.    * @param   request
  1264.    *          The nsIRequest object for the transfer
  1265.    * @param   context
  1266.    *          Additional data
  1267.    * @param   status
  1268.    *          Status code containing the reason for the cessation.
  1269.    */
  1270.   onStopRequest: function(request, context, status) {
  1271.     request.QueryInterface(nsIIncrementalDownload);
  1272.     LOG("UI:DownloadingPage", "onStopRequest: " + request.URI.spec + 
  1273.         ", status = " + status);
  1274.     
  1275.     if (this._downloadThrobber.hasAttribute("state"))
  1276.       this._downloadThrobber.removeAttribute("state");
  1277.     if (this._downloadProgress.mode != "normal")
  1278.       this._downloadProgress.mode = "normal";
  1279.  
  1280.     var u = gUpdates.update;
  1281.     const NS_BINDING_ABORTED = 0x804b0002;
  1282.     switch (status) {
  1283.     case Components.results.NS_ERROR_UNEXPECTED:
  1284.       if (u.selectedPatch.state == STATE_DOWNLOAD_FAILED && 
  1285.           u.isCompleteUpdate) {
  1286.         // Verification error of complete patch, informational text is held in 
  1287.         // the update object.
  1288.         gUpdates.wiz.currentPage = document.getElementById("errors");
  1289.       }
  1290.       else {
  1291.         // Verification failed for a partial patch, complete patch is now
  1292.         // downloading so return early and do NOT remove the download listener!
  1293.         
  1294.         // Reset the progress meter to "undertermined" mode so that we don't 
  1295.         // show old progress for the new download of the "complete" patch.
  1296.         this._downloadProgress.mode = "undetermined";
  1297.         this._pauseButton.disabled = true;
  1298.         
  1299.         var verificationFailed = document.getElementById("verificationFailed");
  1300.         verificationFailed.hidden = false;
  1301.  
  1302.         this._statusFormatter = null;
  1303.         return;
  1304.       }
  1305.       break;
  1306.     case NS_BINDING_ABORTED:
  1307.       LOG("UI:DownloadingPage", "onStopRequest: Pausing Download");
  1308.       // Return early, do not remove UI listener since the user may resume
  1309.       // downloading again.
  1310.       return;
  1311.     case Components.results.NS_OK:
  1312.       LOG("UI:DownloadingPage", "onStopRequest: Patch Verification Succeeded");
  1313.       gUpdates.wiz.canAdvance = true;
  1314.       gUpdates.wiz.advance();
  1315.       break;
  1316.     default:
  1317.       LOG("UI:DownloadingPage", "onStopRequest: Transfer failed");
  1318.       // Some kind of transfer error, die.
  1319.       gUpdates.wiz.currentPage = document.getElementById("errors");
  1320.       break;
  1321.     }
  1322.  
  1323.     var updates = 
  1324.         Components.classes["@mozilla.org/updates/update-service;1"].
  1325.         getService(Components.interfaces.nsIApplicationUpdateService);
  1326.     updates.removeDownloadListener(this);
  1327.   },
  1328.   
  1329.   /**
  1330.    * See nsISupports.idl
  1331.    */
  1332.   QueryInterface: function(iid) {
  1333.     if (!iid.equals(Components.interfaces.nsIRequestObserver) &&
  1334.         !iid.equals(Components.interfaces.nsIProgressEventSink) &&
  1335.         !iid.equals(Components.interfaces.nsISupports))
  1336.       throw Components.results.NS_ERROR_NO_INTERFACE;
  1337.     return this;
  1338.   }
  1339. };
  1340.  
  1341. /**
  1342.  * The "There was an error during the update" page.
  1343.  */
  1344. var gErrorsPage = {
  1345.   /**
  1346.    * Initialize
  1347.    */
  1348.   onPageShow: function() {
  1349.     gUpdates.setButtons(null, true, null, true, null, false, null, true, false, 
  1350.                         null, false);
  1351.     gUpdates.wiz.getButton("finish").focus();
  1352.     
  1353.     var errorReason = document.getElementById("errorReason");
  1354.     errorReason.value = gUpdates.update.statusText;
  1355.     var manualURL = getPref("getCharPref", PREF_UPDATE_MANUAL_URL, "");
  1356.     var errorLinkLabel = document.getElementById("errorLinkLabel");
  1357.     errorLinkLabel.value = manualURL;
  1358.     errorLinkLabel.href = manualURL;
  1359.   },
  1360.   
  1361.   /**
  1362.    * Initialize, for the "Error Applying Patch" case.
  1363.    */
  1364.   onPageShowPatching: function() {
  1365.     gUpdates.wiz.getButton("back").disabled = true;
  1366.     gUpdates.wiz.getButton("cancel").disabled = true;
  1367.     gUpdates.wiz.getButton("next").focus();
  1368.   },
  1369. };
  1370.  
  1371. /**
  1372.  * The "Update has been downloaded" page. Shows information about what
  1373.  * was downloaded.
  1374.  */
  1375. var gFinishedPage = {
  1376.   /**
  1377.    * Called to initialize the Wizard Page.
  1378.    * @param   aDelayRestart
  1379.    *          true if the Restart Application button should be disabled for a
  1380.    *          short delay
  1381.    */
  1382.   onPageShow: function(aDelayRestart) {  
  1383.     var restart = gUpdates.strings.getFormattedString("restartButton",
  1384.       [gUpdates.brandName]);
  1385.     var later = gUpdates.strings.getString("laterButton");
  1386.     gUpdates.setButtons(null, true, null, true, restart, aDelayRestart, later,
  1387.                         false, false, null, false);
  1388.     if (aDelayRestart)
  1389.       setTimeout(this._enableRestartButton, 2000);
  1390.     else
  1391.       gUpdates.wiz.getButton("finish").focus();
  1392.   },
  1393.   
  1394.   /**
  1395.    * Called to initialize the Wizard Page. (Background Source Event)
  1396.    */
  1397.   onPageShowBackground: function() {
  1398.     var finishedBackground = document.getElementById("finishedBackground");
  1399.     finishedBackground.setAttribute("label", gUpdates.strings.getFormattedString(
  1400.       "updateReadyToInstallHeader", [gUpdates.update.name]));
  1401.     // XXXben - wizard should give us a way to set the page header.
  1402.     gUpdates.wiz._adjustWizardHeader();
  1403.     var updateFinishedName = document.getElementById("updateFinishedName");
  1404.     updateFinishedName.value = gUpdates.update.name;
  1405.     
  1406.     var link = document.getElementById("finishedBackgroundLink");
  1407.     link.href = gUpdates.update.detailsURL;
  1408.     
  1409.     this.onPageShow(true);
  1410.  
  1411.     if (getPref("getBoolPref", PREF_UPDATE_TEST_LOOP, false)) {
  1412.       window.restart = function () {
  1413.         gUpdates.wiz.getButton("finish").click();
  1414.       }
  1415.       setTimeout("restart();", UPDATE_TEST_LOOP_INTERVAL);
  1416.     }
  1417.   },
  1418.   
  1419.   /**
  1420.    * Re-enables and focuses the Restart Application button
  1421.    */
  1422.   _enableRestartButton: function() {
  1423.     gUpdates.wiz.canAdvance = true;
  1424.     var finishButton = gUpdates.wiz.getButton("finish");
  1425.     finishButton.disabled = false;
  1426.     finishButton.focus();
  1427.   },
  1428.  
  1429.   /**
  1430.    * Called when the wizard finishes, i.e. the "Restart Now" button is 
  1431.    * clicked. 
  1432.    */
  1433.   onWizardFinish: function() {
  1434.     // Do the restart
  1435.     LOG("UI:FinishedPage" , "onWizardFinish: Restarting Application...");
  1436.     
  1437.     // This process is *extremely* retarded. There should be some nice 
  1438.     // integrated system for determining whether or not windows are allowed
  1439.     // to close or not, and what happens when that happens. We need to 
  1440.     // jump through all these hoops (duplicated from globalOverlay.js) to
  1441.     // ensure that various window types (browser, downloads, etc) all 
  1442.     // allow the app to shut down. 
  1443.     // bsmedberg?     
  1444.  
  1445.     // Notify all windows that an application quit has been requested.
  1446.     var os = Components.classes["@mozilla.org/observer-service;1"]
  1447.                        .getService(Components.interfaces.nsIObserverService);
  1448.     var cancelQuit = 
  1449.         Components.classes["@mozilla.org/supports-PRBool;1"].
  1450.         createInstance(Components.interfaces.nsISupportsPRBool);
  1451.     os.notifyObservers(cancelQuit, "quit-application-requested", null);
  1452.  
  1453.     // Something aborted the quit process. 
  1454.     if (cancelQuit.data)
  1455.       return;
  1456.     
  1457.     // Notify all windows that an application quit has been granted.
  1458.     os.notifyObservers(null, "quit-application-granted", null);
  1459.  
  1460.     // Enumerate all windows and call shutdown handlers
  1461.     var wm =
  1462.         Components.classes["@mozilla.org/appshell/window-mediator;1"].
  1463.         getService(Components.interfaces.nsIWindowMediator);
  1464.     var windows = wm.getEnumerator(null);
  1465.     while (windows.hasMoreElements()) {
  1466.       var win = windows.getNext();
  1467.       if (("tryToClose" in win) && !win.tryToClose())
  1468.         return;
  1469.     }
  1470.     
  1471.     var appStartup = 
  1472.         Components.classes["@mozilla.org/toolkit/app-startup;1"].
  1473.         getService(Components.interfaces.nsIAppStartup);
  1474.     appStartup.quit(appStartup.eAttemptQuit | appStartup.eRestart);
  1475.   },
  1476.   
  1477.   /**
  1478.    * Called when the wizard is canceled, i.e. when the "Later" button is
  1479.    * clicked.
  1480.    */
  1481.   onWizardCancel: function() {
  1482.     var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  1483.                        .getService(Components.interfaces.nsIPromptService);
  1484.     var message = gUpdates.strings.getFormattedString("restartLaterMessage",
  1485.       [gUpdates.brandName]);
  1486.     ps.alert(window, gUpdates.strings.getString("restartLaterTitle"), 
  1487.              message);
  1488.  
  1489.     var interval = getPref("getIntPref", PREF_UPDATE_NAGTIMER_RESTART, 
  1490.                            18000000);
  1491.     gUpdates.registerNagTimer("restart-nag-timer", interval, 
  1492.                               "showUpdateComplete");
  1493.   },
  1494. };
  1495.  
  1496. /**
  1497.  * The "Update was Installed Successfully" page.
  1498.  */
  1499. var gInstalledPage = {
  1500.   /**
  1501.    * Initialize
  1502.    */
  1503.   onPageShow: function() {
  1504.     var ai = 
  1505.         Components.classes["@mozilla.org/xre/app-info;1"].
  1506.         getService(Components.interfaces.nsIXULAppInfo);
  1507.     
  1508.     var branding = document.getElementById("brandStrings");
  1509.     try {
  1510.       var url = branding.getFormattedString("whatsNewURL", [ai.version]);
  1511.       var whatsnewLink = document.getElementById("whatsnewLink");
  1512.       whatsnewLink.href = url;
  1513.       whatsnewLink.hidden = false;
  1514.     }
  1515.     catch (e) {
  1516.     }
  1517.     
  1518.     this.setButtons(null, true, null, true, null, false, null, true, false, 
  1519.                     null, false);
  1520.     gUpdates.wiz.getButton("finish").focus();
  1521.   },
  1522. };
  1523.  
  1524. /**
  1525.  * Called as the application shuts down due to being quit from the File->Quit 
  1526.  * menu item.
  1527.  * XXXben this API is retarded.
  1528.  */
  1529. function tryToClose() {
  1530.   var cp = gUpdates.wiz.currentPage;
  1531.   if (cp.pageid != "finished" && cp.pageid != "finishedBackground")
  1532.     gUpdates.onWizardCancel();
  1533.   return true;
  1534. }
  1535.  
  1536. /**
  1537.  * Callback for the Update Prompt to set the current page if an Update Wizard
  1538.  * window is already found to be open.
  1539.  * @param   pageid
  1540.  *          The ID of the page to switch to
  1541.  */
  1542. function setCurrentPage(pageid) {
  1543.   gUpdates.wiz.currentPage = document.getElementById(pageid);
  1544. }
  1545.  
  1546.